#pragma	once

#include "ZStream.h"
#include "ZOdbcConnection.h"
#include <sql.h>
#include <sqlext.h>
#include <vector>
#include <string>
#include <boost/type_traits.hpp>
#include <boost/type_traits/alignment_of.hpp>

namespace	Z	{

namespace	Odbc	{

//------------------------------------------------------------------------------------------------------------------------
class	_ZOdbcApi	Parameter
{
public:
	virtual	~Parameter();
	virtual	bool	bind(SQLHSTMT hstmt, SQLUSMALLINT ipar)	= 0;
	virtual	SQLRETURN	putData(SQLHSTMT hstmt)	= 0;
protected:
	Parameter(SQLINTEGER kValLen);
	static	bool	bind(SQLHSTMT hstmt, SQLUSMALLINT ipar, SQLSMALLINT fCType, SQLSMALLINT fSqlType, SQLULEN cbColDef, SQLPOINTER rgbValue, SQLLEN* pcbValue);

protected:
	SQLINTEGER	mValLen;
};

//------------------------------------------------------------------------------------------------------------------------
template<typename T>	struct	ArithmeticParameterTraits;

template<>
struct	ArithmeticParameterTraits<unsigned char>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_UTINYINT;
	static	const	SQLSMALLINT	fSqlType	= SQL_TINYINT;
};

template<>
struct	ArithmeticParameterTraits<short>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_SSHORT;
	static	const	SQLSMALLINT	fSqlType	= SQL_SMALLINT;
};

template<>
struct	ArithmeticParameterTraits<unsigned short>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_USHORT;
	static	const	SQLSMALLINT	fSqlType	= SQL_SMALLINT;
};

template<>
struct	ArithmeticParameterTraits<int>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_SLONG;
	static	const	SQLSMALLINT	fSqlType	= SQL_INTEGER;
};

template<>
struct	ArithmeticParameterTraits<unsigned int>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_ULONG;
	static	const	SQLSMALLINT	fSqlType	= SQL_INTEGER;
};

template<>
struct	ArithmeticParameterTraits<__int64>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_SBIGINT;
	static	const	SQLSMALLINT	fSqlType	= SQL_BIGINT;
};

template<>
struct	ArithmeticParameterTraits<unsigned __int64>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_UBIGINT;
	static	const	SQLSMALLINT	fSqlType	= SQL_BIGINT;
};

template<>
struct	ArithmeticParameterTraits<float>
{
	static	const	SQLSMALLINT	fCType	= SQL_C_FLOAT;
	static	const	SQLSMALLINT	fSqlType	= SQL_REAL;
};

//------------------------------------------------------------------------------------------------------------------------
template<typename T>
class	ArithmeticParameter
	:	public Parameter
{
public:
	ArithmeticParameter(T kValue)
	:	Parameter(0),
		mValue(kValue)	{
	}
private:
	virtual	bool	bind(SQLHSTMT hstmt, SQLUSMALLINT ipar)	{
		return Parameter::bind(hstmt, ipar, ArithmeticParameterTraits<T>::fCType, ArithmeticParameterTraits<T>::mSqlType, 0, &mValue, &mValLen);
	}
	virtual	SQLRETURN	putData(SQLHSTMT hstmt)	{	return SQL_ERROR;	}

private:
	T	mValue;
};

//------------------------------------------------------------------------------------------------------------------------
class	StringParameter
	:	public Parameter
{
public:
	StringParameter(const std::string& kString)
	:	Parameter(SQL_NTS),
		mString(kString)	{
	}
private:
	virtual	bool	bind(SQLHSTMT hstmt, SQLUSMALLINT ipar)	{
		return Parameter::bind(hstmt, ipar, SQL_C_CHAR, SQL_VARCHAR, mString.length(), (SQLPOINTER)mString.c_str(), &mValLen);
	}
	virtual	SQLRETURN	putData(SQLHSTMT hstmt)	{	return SQL_ERROR;	}

private:
	std::string	mString;
};

//------------------------------------------------------------------------------------------------------------------------
class	_ZOdbcApi	StreamParameter
	:	public Parameter
{
public:
	StreamParameter(const Z::Stream& kStream)
	:	Parameter(SQL_LEN_DATA_AT_EXEC((SQLLEN)kStream.size())),
		mStream(kStream)	{
	}
private:
	virtual	bool	bind(SQLHSTMT hstmt, SQLUSMALLINT ipar)	{
		return Parameter::bind(hstmt, ipar, SQL_C_BINARY, SQL_VARBINARY, mStream.size(), this, &mValLen);
	}
	virtual	SQLRETURN	putData(SQLHSTMT hstmt)	{
		const size_t kBatchSize	= 256;
		const size_t kStreamSize	= mStream.size();
		size_t kWrittenSize	= 0;
		SQLRETURN retcode	= SQL_NEED_DATA;
		while(kWrittenSize < kStreamSize)	{
			const size_t kPendingSize	= kStreamSize - kWrittenSize;
			const size_t kWriteSize	= std::min<size_t>(kBatchSize, kPendingSize);
			SQLRETURN retcode	= ::SQLPutData(hstmt, mStream.getBuffer() + kWrittenSize, kWriteSize);
			if(!SQL_SUCCEEDED(retcode))
				return retcode;
			kWrittenSize	+= kWriteSize;
		}
		return retcode;
	}

private:
	Z::Stream		mStream;
};

//------------------------------------------------------------------------------------------------------------------------
class	_ZOdbcApi	Parameters
{
public:
	Parameters();
	~Parameters();
	template<typename T>	void	add(T t, Connection& kConnection)	{
		BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);
		BOOST_STATIC_ASSERT(!boost::is_pointer<T>::value);
		void* memory	= kConnection.allocateAligned(sizeof(ArithmeticParameter<T>), boost::alignment_of<ArithmeticParameter<T> >::value);
		mParameters.push_back(new(memory)ArithmeticParameter<T>(t));
	}
	void	add(const std::string& str, Connection& kConnection)	{
		void* memory	= kConnection.allocateAligned(sizeof(StringParameter), boost::alignment_of<StringParameter>::value);
		mParameters.push_back(new(memory) StringParameter(str));
	}
	void	add(const Z::Stream& kStream, Connection& kConnection)	{
		void* memory	= kConnection.allocateAligned(sizeof(StreamParameter), boost::alignment_of<StreamParameter>::value);
		mParameters.push_back(new(memory) StreamParameter(kStream));
	}
	bool	bind(SQLHSTMT hstmt);
	SQLRETURN	putData(SQLHSTMT hstmt);

private:
	typedef	std::vector<Parameter*>	ParameterVector;
	ParameterVector	mParameters;
};

}

}